/**
 * @file PlayState.cpp
 *
 * Implementación de PlayState.h
 *
 * @see PlayState.h
 */

#include <PlayState.h>
#include <PauseState.h>
#include <EndState.h>
#include <IntroState.h>

template<> PlayState* Ogre::Singleton<PlayState>::ms_Singleton = 0;

PlayState::PlayState()
{
  initPieces();
  std::srand(time(NULL));
}

PlayState::~PlayState() throw() {}

void PlayState::enter()
{
  _root = Ogre::Root::getSingletonPtr();

  // Se recupera el gestor de escena y la cámara.
  _sceneMgr = _root->getSceneManager("SceneManager");
  _camera = _sceneMgr->getCamera("MainCamera");

  Ogre::SceneNode* cameraPosition = _sceneMgr->createSceneNode();
  cameraPosition->attachObject(_camera);
  cameraPosition->setPosition(0, -SCENEHEIGHT / 4, 40);
  cameraPosition->pitch(Ogre::Degree(10));

  _viewport = _root->getAutoCreatedWindow()->addViewport(_camera);

  // Nuevo background colour.
  _viewport->setBackgroundColour(Ogre::ColourValue(0.0, 0.0, 0.0));

  _exitGame = false;

  // Crear cuadrícula para las fichas
  _scene = std::list<std::vector<int> >(SCENEHEIGHT);

  // Crear nodo para representar el tetris y su contenido
  _sceneNode = _sceneMgr->createSceneNode("Cuadricula");
  _sceneNode->translate(-SCENEWIDTH / 2, SCENEHEIGHT / 2, 0);
  _sceneMgr->getRootSceneNode()->addChild(_sceneNode);

  _rectangleNode = buildRectangle();
  _rectangleNode->translate(-SCENEWIDTH / 2, SCENEHEIGHT / 2, 0);
  _sceneMgr->getRootSceneNode()->addChild(_rectangleNode);

  _score = 0; // Inicializar puntuacion

  for (std::list<std::vector<int> >::iterator it = _scene.begin();
      it != _scene.end(); ++it)
    *it = std::vector<int>(SCENEWIDTH, 0);

  _timeToFall = 0.0;
  _timeMin = 0.0;
  _getNewPiece = true;
  _rotation = -1;
  _pieceColor = -1;
  _rotatePiece = false;
  _yoffset = 0;
  _rapidFall = false;
  _drawInThisIteration = false;

  // Audio
  _pTrackManager = new TrackManager;
  _pSoundFXManager = new SoundFXManager;

  _pTrackManager->load("Korobeiniki.wav")->play();
}

void PlayState::exit()
{
  _pTrackManager->load("Korobeiniki.wav")->stop();
  delete _pSoundFXManager;
  delete _pTrackManager;

  // TODO munmap_chunk(): invalid pointer por limpiar la escena
  _sceneMgr->clearScene();
  _root->getAutoCreatedWindow()->removeAllViewports();
  _scene.clear();
}

void PlayState::pause() {
  _pTrackManager->load("Korobeiniki.wav")->pause(); }

void PlayState::resume() {
  _pTrackManager->load("Korobeiniki.wav")->play(); }

bool PlayState::frameStarted(const Ogre::FrameEvent& evt)
{
  _timeToFall += evt.timeSinceLastFrame;
  _timeMin += evt.timeSinceLastFrame;

  if (_timeMin >= TIMEMIN)
  {
    _timeMin = 0.0;

    if (_getNewPiece and not generate())
    {
      pushState(EndState::getSingletonPtr());
      return true;
    }

    if (_rotatePiece)
      rotate();

    if (_yoffset != 0)
      move(_yoffset);

    if (_rapidFall)
      fall();

    _drawInThisIteration = true;
  }

  if (_timeToFall >= TIMETOFALL)
  {
    _timeToFall = 0.0;
    fall();
    _drawInThisIteration = true;
  }

  if (_drawInThisIteration)
  {
    drawScene();
    _drawInThisIteration = false;
  }

  return true;
}

bool PlayState::frameEnded(const Ogre::FrameEvent& evt)
{
  return not _exitGame;
}

void PlayState::keyPressed(const OIS::KeyEvent& e)
{
  switch(e.key)
  {
    case OIS::KC_ESCAPE:
      popState(); break;

    // Tecla p => PauseState.
    case OIS::KC_P:
      pushState(PauseState::getSingletonPtr()); break;

    // Tecla ^ (arriba) => Rotar
    case OIS::KC_UP:
      _rotatePiece = true; break;

    // Teclas <-,-> => Desplazar izquierda, derecha
    case OIS::KC_LEFT:
      _yoffset = -1; break;
    case OIS::KC_RIGHT:
      _yoffset = 1; break;

    // Tecla v (abajo) => Bajada rápida
    case OIS::KC_DOWN:
      _rapidFall = true; break;

    default:
      break;
  }
}

void PlayState::keyReleased(const OIS::KeyEvent& e)
{
  switch(e.key)
  {
    // Tecla ^ (arriba) => Rotar
    case OIS::KC_UP:
      _rotatePiece = false; break;

    // Teclas <-,-> => Desplazar izquierda, derecha
    case OIS::KC_LEFT:
      _yoffset = 0; break;
    case OIS::KC_RIGHT:
      _yoffset = 0; break;

    // Tecla v (abajo) => Bajada rápida
    case OIS::KC_DOWN:
      _rapidFall = false; break;

    default:
      break;
  }
}

void PlayState::mouseMoved(const OIS::MouseEvent& e) {}

void PlayState::mousePressed(const OIS::MouseEvent& e, OIS::MouseButtonID id) {}

void PlayState::mouseReleased(const OIS::MouseEvent& e, OIS::MouseButtonID id) {}

PlayState* PlayState::getSingletonPtr() { return ms_Singleton; }

PlayState& PlayState::getSingleton()
{ 
  assert(ms_Singleton);
  return *ms_Singleton;
}

void PlayState::initPieces()
{
  /*
   * Cada pieza se compone de 4 cuadraditos y es representada por un vector de
   * 4 posiciones, en el que cada par representa la posición del cuadradito en
   * una matriz 4x4.
   */

  _pieces = std::vector<std::vector<std::vector<Position> > >(7);

  // Pieza tipo T

  _pieces.at(0) = std::vector<std::vector<Position> >(4);

  Position arrayT0[] = { {0, 1}, {1, 0}, {1, 1}, {1, 2} };
  _pieces.at(0).at(0) =
    std::vector<Position>(arrayT0, arrayT0 + sizeof(arrayT0)/sizeof(Position));

  Position arrayT1[] = { {0, 0}, {1, 0}, {1, 1}, {2, 0} };
  _pieces.at(0).at(1) =
    std::vector<Position>(arrayT1, arrayT1 + sizeof(arrayT1)/sizeof(Position));

  Position arrayT2[] = { {0, 0}, {0, 1}, {0, 2}, {1, 1} };
  _pieces.at(0).at(2) =
    std::vector<Position>(arrayT2, arrayT2 + sizeof(arrayT2)/sizeof(Position));

  Position arrayT3[] = { {0, 1}, {1, 0}, {1, 1}, {2, 1} };
  _pieces.at(0).at(3) =
    std::vector<Position>(arrayT3, arrayT3 + sizeof(arrayT3)/sizeof(Position));

  // Pieza tipo Z

  _pieces.at(1) = std::vector<std::vector<Position> >(2);

  Position arrayZ0[] = { {0, 0}, {0, 1}, {1, 1}, {1, 2} };
  _pieces.at(1).at(0) =
    std::vector<Position>(arrayZ0, arrayZ0 + sizeof(arrayZ0)/sizeof(Position));

  Position arrayZ1[] = { {0, 1}, {1, 0}, {1, 1}, {2, 0} };
  _pieces.at(1).at(1) =
    std::vector<Position>(arrayZ1, arrayZ1 + sizeof(arrayZ1)/sizeof(Position));

  // Pieza tipo S

  _pieces.at(2) = std::vector<std::vector<Position> >(2);

  Position arrayS0[] = { {0, 1}, {0, 2}, {1, 0}, {1, 1} };
  _pieces.at(2).at(0) =
    std::vector<Position>(arrayS0, arrayS0 + sizeof(arrayS0)/sizeof(Position));

  Position arrayS1[] = { {0, 0}, {1, 0}, {1, 1}, {2, 1} };
  _pieces.at(2).at(1) =
    std::vector<Position>(arrayS1, arrayS1 + sizeof(arrayS1)/sizeof(Position));

  // Pieza tipo I

  _pieces[3] = std::vector<std::vector<Position> >(2);

  Position arrayI0[] = { {0, 0}, {0, 1}, {0, 2}, {0, 3} };
  _pieces.at(3).at(0) =
    std::vector<Position>(arrayI0, arrayI0 + sizeof(arrayI0)/sizeof(Position));

  Position arrayI1[] = { {0, 0}, {1, 0}, {2, 0}, {3, 0} };
  _pieces.at(3).at(1) =
    std::vector<Position>(arrayI1, arrayI1 + sizeof(arrayI1)/sizeof(Position));

  // Pieza tipo O (el cubo)

  _pieces.at(4) = std::vector<std::vector<Position> >(1);

  Position arrayO[] = { {0, 0}, {0, 1}, {1, 0}, {1, 1} };
  _pieces.at(4).at(0) =
    std::vector<Position>(arrayO, arrayO + sizeof(arrayO)/sizeof(Position));

  // Pieza tipo L

  _pieces.at(5) = std::vector<std::vector<Position> >(4);

  Position arrayL0[] = { {0, 0}, {1, 0}, {2, 0}, {2, 1} };
  _pieces.at(5).at(0) =
    std::vector<Position>(arrayL0, arrayL0 + sizeof(arrayL0)/sizeof(Position));

  Position arrayL1[] = { {0, 0}, {0, 1}, {0, 2}, {1, 0} };
  _pieces.at(5).at(1) =
    std::vector<Position>(arrayL1, arrayL1 + sizeof(arrayL1)/sizeof(Position));

  Position arrayL2[] = { {0, 0}, {0, 1}, {1, 1}, {2, 1} };
  _pieces.at(5).at(2) =
    std::vector<Position>(arrayL2, arrayL2 + sizeof(arrayL2)/sizeof(Position));

  Position arrayL3[] = { {0, 2}, {1, 0}, {1, 1}, {1, 2} };
  _pieces.at(5).at(3) =
    std::vector<Position>(arrayL3, arrayL3 + sizeof(arrayL3)/sizeof(Position));

  // Pieza tipo J

  _pieces.at(6) = std::vector<std::vector<Position> >(4);

  Position arrayJ0[] = { {0, 1}, {1, 1}, {2, 0}, {2, 1} };
  _pieces.at(6).at(0) =
    std::vector<Position>(arrayJ0, arrayJ0 + sizeof(arrayJ0)/sizeof(Position));

  Position arrayJ1[] = { {0, 0}, {1, 0}, {1, 1}, {1, 2} };
  _pieces.at(6).at(1) =
    std::vector<Position>(arrayJ1, arrayJ1 + sizeof(arrayJ1)/sizeof(Position));

  Position arrayJ2[] = { {0, 0}, {0, 1}, {1, 0}, {2, 0} };
  _pieces.at(6).at(2) =
    std::vector<Position>(arrayJ2, arrayJ2 + sizeof(arrayJ2)/sizeof(Position));

  Position arrayJ3[] = { {0, 0}, {0, 1}, {0, 2}, {1, 2} };
  _pieces.at(6).at(3) =
    std::vector<Position>(arrayJ3, arrayJ3 + sizeof(arrayJ3)/sizeof(Position));
}

bool PlayState::generate()
{
  _currentPiecePosition.y = SCENEWIDTH / 2 - 1;
  _currentPiecePosition.x = 0;

  int pieceType = rand() % _pieces.size();
  _currentPieceType = &( _pieces.at(pieceType) );

  _rotation = rand() % _currentPieceType->size();
  _currentPiece = &( _currentPieceType->at(_rotation) );

  // Fijar el color de la pieza
  _pieceColor = pieceType + 1;

  std::vector<Position> newPosition(4);

  for (int i = 0; i < 4; ++i)
  {
    // Calcula la posición de cada cuadradito de la nueva pieza.
    newPosition.at(i).x = _currentPiece->at(i).x + _currentPiecePosition.x;
    newPosition.at(i).y = _currentPiece->at(i).y + _currentPiecePosition.y;

    // Si la posición estaba ocupada, se acabó la partida.
    std::list<std::vector<int> >::iterator sceneIt = _scene.begin();
    advance(sceneIt, newPosition.at(i).x);
    if ( sceneIt->at(newPosition.at(i).y) != 0 )
    {
      char name[] = "EDV";
      RecordManager* pRecordMgr = RecordManager::getRecordManager();
      pRecordMgr->readRecords(RECORDFILE);
      pRecordMgr->insertRecord(name, _score);
      pRecordMgr->writeRecords(RECORDFILE);

      std::cout << "GAME OVER" << std::endl;

      return false;
    }
  }

  _getNewPiece = false;
  return true;
}

void PlayState::rotate()
{
  // Selecciona la siguiente rotación.
  int newRotation = (_rotation + 1) % _currentPieceType->size();
  std::vector<Position>* newCurrentPiece = &( _currentPieceType->at(newRotation) );

  std::vector<Position> newPosition(4);

  for (int i = 0; i < 4; ++i)
  {
    // Calcula la posición de cada cuadradito de la nueva rotación.
    newPosition.at(i).x = newCurrentPiece->at(i).x + _currentPiecePosition.x;
    newPosition.at(i).y = newCurrentPiece->at(i).y + _currentPiecePosition.y;

    // Si se sale de la escena o la nueva posición estaba ocupada, no la rota.
    std::list<std::vector<int> >::iterator sceneIt = _scene.begin();
    advance(sceneIt, newPosition.at(i).x);
    if (newPosition.at(i).x < 0 or
        newPosition.at(i).x >= SCENEHEIGHT or
        newPosition.at(i).y < 0 or
        newPosition.at(i).y >= SCENEWIDTH or
        sceneIt->at(newPosition.at(i).y) != 0)
    {
      return;
    }
  }

  _rotation = newRotation;
  _currentPiece = newCurrentPiece;
}

void PlayState::move(const int y)
{
  std::vector<Position> newPosition(4);

  for (int i = 0; i < 4; ++i)
  {
    // Calcula la posición de cada cuadradito de la pieza ya movida.
    newPosition.at(i).x = _currentPiece->at(i).x + _currentPiecePosition.x;
    newPosition.at(i).y = _currentPiece->at(i).y + _currentPiecePosition.y + y;

    // Si se sale de la escena o la nueva posición estaba ocupada, no la mueve.
    std::list<std::vector<int> >::iterator sceneIt = _scene.begin();
    advance(sceneIt, newPosition.at(i).x);
    if (newPosition.at(i).y < 0 or
        newPosition.at(i).y >= SCENEWIDTH or
        sceneIt->at(newPosition.at(i).y) != 0)
    {
      return;
    }
  }

  _currentPiecePosition.y += y;
}

void PlayState::fall()
{
  std::vector<Position> newPosition(4);

  for (int i = 0; i < 4; ++i)
  {
    // Calcula la posición de cada cuadradito de la pieza ya movida.
    newPosition.at(i).x = _currentPiece->at(i).x + _currentPiecePosition.x + 1;
    newPosition.at(i).y = _currentPiece->at(i).y + _currentPiecePosition.y;

    // Si se sale de la escena o la nueva posición estaba ocupada, la pieza ha
    // tocado fondo.
    std::list<std::vector<int> >::iterator sceneIt = _scene.begin();
    advance(sceneIt, newPosition.at(i).x);

    if ((newPosition.at(i).x >= SCENEHEIGHT or
          sceneIt->at(newPosition.at(i).y) != 0) and
        _getNewPiece == false)
    {
      _getNewPiece = true;
      save();
      return;
    }
  }

  _currentPiecePosition.x++;
}

void PlayState::save()
{
  for (int i = 0; i < 4; ++i)
  {
    // Calcula la posición de cada cuadradito de la pieza.
    int x = _currentPiece->at(i).x + _currentPiecePosition.x;
    int y = _currentPiece->at(i).y + _currentPiecePosition.y;

    std::list<std::vector<int> >::iterator sceneIt = _scene.begin();
    advance(sceneIt, x);
    sceneIt->at(y) = _pieceColor;
  }

  searchCompleteLines();
}

  void
PlayState::drawScene()
{
  int posx, posy;

  _sceneNode->removeAndDestroyAllChildren();

  // Dibujar pieza

  for (int i = 0; i < 4; i++)
  {

    posy = _currentPiece->at(i).x + _currentPiecePosition.x;
    posx = _currentPiece->at(i).y + _currentPiecePosition.y;

    // Crear nodo y entidad para el cuadrado actual
    Ogre::SceneNode* cubeNode = _sceneMgr->createSceneNode();

    std::ostringstream cubeMeshNameStr;
    cubeMeshNameStr << "Cube" << getCubeColor(_pieceColor) << ".mesh";

    Ogre::Entity* cubeEnt = _sceneMgr->createEntity(cubeMeshNameStr.str());
    cubeNode->attachObject(cubeEnt);

    // Posicionar el nodo y añadirlo al escenario
    cubeNode->translate(posx, -posy, 0);
    _sceneNode->addChild(cubeNode);

  }

  // Dibujar escenario

  // Dibujar filas
  std::list<std::vector<int> >::iterator rowIt;
  for (rowIt = _scene.begin(), posy = 0;
      rowIt != _scene.end();
      rowIt++, posy--)
  {

    // Dibujar columnas
    std::vector<int>::iterator colIt;
    for (colIt = (*rowIt).begin(), posx = 0;
        colIt != (*rowIt).end();
        colIt++, posx++)
    {

      if (*colIt == 0) continue;

      // Crear nodo y entidad para el cuadrado actual
      Ogre::SceneNode* cubeNode = _sceneMgr->createSceneNode();

      std::ostringstream cubeMeshNameStr;
      std::string cubeColor = getCubeColor(*colIt);
      cubeMeshNameStr << "Cube" << cubeColor << ".mesh";

      Ogre::Entity* cubeEnt = _sceneMgr->createEntity(cubeMeshNameStr.str());
      cubeNode->attachObject(cubeEnt);

      // Posicionar el nodo y añadirlo al escenario
      cubeNode->translate(posx, posy, 0);
      _sceneNode->addChild(cubeNode);
    }
  }

  // Dibujar puntuación

  // Crear nodo para la puntuación
  Ogre::SceneNode* scoreNode = _sceneMgr->createSceneNode();

  // Crear string para la puntuación
  std::ostringstream scoreStrStream;
  scoreStrStream << _score;

  std::string scoreStr;
  scoreStr = scoreStrStream.str();

  // Invertir string de la puntuación para que crezca hacia la izquierda
  std::reverse(scoreStr.begin(), scoreStr.end());

  // Recorrer string de la puntuación
  std::string::iterator scoreStrIt;
  for (scoreStrIt = scoreStr.begin(), posx = 0;
      scoreStrIt != scoreStr.end();
      scoreStrIt++, posx -= 4)
  {

    // Crear string con el nombre del mesh
    std::ostringstream letterStrStream;

    letterStrStream << "LETTER_" << *scoreStrIt << ".mesh";

    // Crear entidad con la mesh
    Ogre::Entity* letterEnt = _sceneMgr->createEntity(letterStrStream.str());

    //letterEnt->setQueryFlags(_QUERY_MASK_STAGE_);
    //letterEnt->setLightMask(_LIGHT_MASK_MENU_);
    //letterEnt->setCastShadows(false);

    // Crear nodo para la letra
    Ogre::SceneNode* letterNode = _sceneMgr->createSceneNode();

    letterNode->attachObject(letterEnt);
    letterNode->translate(Ogre::Vector3(posx, 0, 0));
    scoreNode->addChild(letterNode);
  }

  // Colocamos el marcador en su lugar
  scoreNode->translate(Ogre::Vector3((SCENEWIDTH / 2) + 4 * 5, -4, 0));
  _sceneNode->addChild(scoreNode);

}

  Ogre::SceneNode*
PlayState::buildRectangle()
{

  Ogre::SceneNode* rectangleNode;
  Ogre::SceneNode* brickNode;
  Ogre::Entity* brickEnt;

  rectangleNode = _sceneMgr->createSceneNode();

  // Dibujar filas
  for (int y = 1; y >= -SCENEHEIGHT; y--)
  {

    // Dibujar columnas

    // Primera y ultima fila rellenas por completo
    if (y == 1 || y == -SCENEHEIGHT)
    {

      for (int x = -1; x <= SCENEWIDTH; x++)
      {

        brickNode = _sceneMgr->createSceneNode();
        brickEnt = _sceneMgr->createEntity("CubeGrey.mesh");
        brickNode->attachObject(brickEnt);
        brickNode->setPosition(x, y, 0);
        rectangleNode->addChild(brickNode);
      }
    }

    // Para el resto de filas solo se pintan los lados
    else
    {
      brickNode = _sceneMgr->createSceneNode();
      brickEnt = _sceneMgr->createEntity("CubeGrey.mesh");
      brickNode->attachObject(brickEnt);
      brickNode->setPosition(-1, y, 0);
      rectangleNode->addChild(brickNode);

      brickNode = _sceneMgr->createSceneNode();
      brickEnt = _sceneMgr->createEntity("CubeGrey.mesh");
      brickNode->attachObject(brickEnt);
      brickNode->setPosition(SCENEWIDTH, y, 0);		
      rectangleNode->addChild(brickNode);
    }
  }

  return rectangleNode;
}

  std::string
PlayState::getCubeColor(const int pieceType)
{

  std::string pieceColor;

  switch (pieceType)
  {

    case YELLOW:
      pieceColor = "Yellow";
      break;
    case ORANGE:
      pieceColor = "Orange";
      break;
    case GREEN:
      pieceColor = "Green";
      break;
    case RED:
      pieceColor = "Red";
      break;
    case CYAN:
      pieceColor = "Cyan";
      break;
    case BLUE:
      pieceColor = "Blue";
      break;
    case MAGENTA:
      pieceColor = "Magenta";
      break;
    default:
      pieceColor = "Grey";
  }

  return pieceColor;
}

bool
PlayState::completeLine(std::vector<int> line) {

  std::vector<int>::iterator it;
  for (it = line.begin();
      it != line.end();
      it++)
    if (*it == 0) return false;

  return true;
}

void
PlayState::searchCompleteLines() {

  int completeLines = 0;

  std::list<std::vector<int> >::iterator it;
  for (it = _scene.begin();
      it != _scene.end();
      it++)

    if (completeLine(*it)) {
      std::fill((*it).begin(), (*it).end(), 0);
      _scene.splice(_scene.begin(), _scene, it);
      completeLines++;
    }

  _score += completeLines * completeLines * SCOREMULT;
}

